import re
import asyncio
import logging

from asyncio.streams import StreamWriter, StreamReader, IncompleteReadError

from .router import Router
from .utils import RouteRule
from .request import Request
from .response import Response, JsonResponse

# 用来从请求头中匹配出kv值，方便后续的操作
PATTERN = r"(?P<key>.*): (?P<value>.*)(?=\r)"
REGEX = re.compile(PATTERN)

DATAFMT = "%Y/%m/%d-%H:%M:%S"
FORMAT = "%(lineno)d %(asctime)s %(processName)s %(process)d %(threadName)s %(thread)d   %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATAFMT)


class Power:
    Router = Router
    Response = Response

    # register function
    def route(self, path, methods="GET POST"):
        if isinstance(methods, str):
            methods = list(map(lambda x: x.upper(), methods.split()))

        def _route(fn):
            route_rule = RouteRule(path, methods, fn)
            for method in methods:
                # 程序员写路由的时候该方法框架不支持时，只能注册到GET方法集合里
                rule_list: list = getattr(self.Router.METHODS, method, self.Router.METHODS.GET)
                rule_list.append(route_rule)
                # self.Router.route_rule_list.append(route_rule)

        return _route

    def get(self, path):
        return self.route(path, methods="GET")

    def post(self, path):
        return self.route(path, methods="POST")

    def get_view_fn(self, url_prefix, path="", method="", where=None):

        if where is None:
            where = self.Router.METHODS

        route_rule_list: list = getattr(where, method, self.Router.METHODS.GET)

        if path:
            url_prefix = path

        for rule in route_rule_list:
            rule: RouteRule = rule
            # TODO 支持正则表达式

            if rule.path == url_prefix:
                return rule.fn
        return

    def dispatch(self, request):
        method = request.method

        path: str = request.path[1:]

        # TODO 此处需要进行判断，根据左前缀匹配进行分流，提高效率
        # TODO 此处只实现了一级路由O(1)，后续在考虑具体实现更多层级的路由
        url_prefix, _, path = path.partition("/")

        # 1. /
        if url_prefix == path == "":
            url_prefix = "/"

            fn = self.get_view_fn(url_prefix, method=method)

        else:
            url_prefix = "/" + url_prefix

            # 2. /abc
            if not path:
                fn = self.get_view_fn(url_prefix, method=method)

            # 3. /abc/def
            else:
                path = "/" + path
                fn = self.get_view_fn(url_prefix, path, method, self.Router.ROUTERS.get(url_prefix))

        return fn or self.default

    @classmethod
    def beautify(cls, response) -> bytes:
        """
        根据给定的返回值进行一点判断后返回，一些的底层的数据类型放到请求头里，等等繁琐的操作，框架内部替你解决了
        所以，你的函数或协程函数返回时，可以返回字符串，bytes, dict等类型
        :param response: 你的主体返回值
        :return: 最终是一个bytes
        """

        if isinstance(response, bytes):
            response = response.decode()

        if isinstance(response, str):
            response = Response(response)

        if isinstance(response, dict):
            response = JsonResponse(response)

        if isinstance(response, (Response, JsonResponse)):
            encoding = response.charset

            response = str(response)

            response = response.encode(encoding=encoding)

        if not isinstance(response, bytes):
            response = bytes(response)

        return response

    async def main(self, reader: StreamReader, writer: StreamWriter):

        # 先读取请求HTTP请求头部分的内容
        try:
            headers = (await reader.readuntil(b"\r\n\r\n")).decode()
        except IncompleteReadError as e:
            print(e)
            return
        # 对http请求头的第一行进行处理
        method, path, scheme = headers.partition("\r\n")[0].split()

        # 对剩余的请求头进行处理，包装到一个Request对象内部，将常用的属性挂到对象上，方便使用
        headers = dict(REGEX.findall(headers))
        request = Request(headers, method, path, scheme)
        logging.info(request)
        # 对请求体进行处理，其实，只有POST跟PUT等方法才有请求体，所以先进行判断，如果不是POST请求，则不进行后续的读取操作

        if method in ["POST", "PUT"]:
            body = bytearray()

            # 当内容指针不在EOF处时就要继续向下读取一行，在确定时候，将类文件指针移动到EOF处

            while reader.at_eof():
                # TODO 该函数的位置
                reader.feed_eof()
                body.extend(await reader.readline())

            # 将HTTP的响应体绑定到Request对象内部
            request.set_body(body)

        # 根据已有的内容进行分发，获取到一个视图函数或者视图协程函数，我们要写的就是这个视图函数本身
        view_fn = self.dispatch(request)

        # TODO 可能不能直接 await，最好写一种支持写普通的函数
        response = await view_fn(request)

        # 用户（WEB程序员）的视图函数可能返回的不标准，所以我们要对其返回值进行一点美化，beautiful返回的是一个bytes对象
        response = self.beautify(response)

        # 将该HTTP报文返回给客户端
        writer.write(response)

        # TODO 准备结束，一次请求结束，直接关闭可能不太好，要根据是否为长连接进行相应修改
        writer.write_eof()
        await writer.drain()
        writer.close()

    @classmethod
    async def default(cls, request):
        """
        当所有路由都不匹配时，调用该函数
        :param request:
        :return:
        """
        del request
        resp = Response("<h1>404 Not Found</h1>", status=404)
        return resp

    def run(self, ip="127.0.0.1", port=80):
        asyncio.run(self._run(ip, port))

    async def _run(self, ip, port):
        server = await asyncio.start_server(self.main, host=ip, port=port)
        print(f"serve on {ip}:{port}")
        await server.serve_forever()
